#include <Lucnx/string.h>
#include <Lucnx/stdlib.h>
#include <Lucnx/printk.h>
#include <Lucnx/debug.h>
#include <Lucnx/device.h>
#include <Lucnx/io.h>

#define MEM_BASE 0xb8000
#define SCREEN_SIZE 0x4000

static u16 pos;
static u8 state;
static u32 screen;

typedef struct console_t
{
    u32 pos;
    u32 screen;
    u8 state;
}console_t;

static console_t *current_console;
static console_t console_table[2];

int console_write(void *ptr, char *str, int count);

enum
{
    STATE_NORMAL,
    STATE_ESC
};

static void get_cursor()
{
    outb(0x3d4,0x0e);
    pos = inb(0x3d5) << 9;
    outb(0x3d4,0x0f);
    pos |= inb(0x3d5) << 1;
}

static void set_cursor()
{
    u16 _pos = pos + (screen - MEM_BASE); // 转化为在显存中的光标位置

    outb(0x3d4, 0x0e);
    outb(0x3d5, _pos >> 9);
    outb(0x3d4, 0x0f);
    outb(0x3d5, _pos >> 1);
}

static void get_screen()
{
    outb(0x3d4, 0x0c);
    screen |= inb(0x3d5) << 9;
    outb(0x3d4, 0x0d);
    screen |= inb(0x3d5) << 1;

    screen += MEM_BASE;
}

static void set_screen()
{
    outb(0x3d4, 0x0c);
    outb(0x3d5, (screen - MEM_BASE) >> 9);
    outb(0x3d4, 0x0d);
    outb(0x3d5, (screen - MEM_BASE) >> 1);
}

// 将后 1840 个的字符复制到前 1840 个位置, 清空最后 1 行
static void scroll_up()
{
    memcpy((void *) screen, (void *) screen + 160, 3840);
    u16 *ptr = (u16 *) (screen + 3840);

    for (int i = 0; i < 80; i++)
    {
        *ptr++ = 0x720;
    }
    pos = 3840;
}

// 换行
inline static void com_lf()
{
    if (pos >= 3840)
    {
        scroll_up();
        pos = 3840;
        return;
    }
    pos += 160;
}

// 将光标移到行首
inline static void com_cr()
{
    u8 x = pos % 160;
    pos -= x;
}

// 退格
inline static void com_bs()
{
    // 没有退路了
    if (pos == 0)
    {
        return;
    }
    
    pos -= 2;
    char *ptr = (char *) pos + screen;
    *ptr = 0x20;
}

static inline void state_normal(char c)
{
    switch (c)
    {
    case '\n':
        com_lf(); // 换行时并且回车
    case '\r':
        com_cr();
        break;
    case '\b':
        com_bs();
        break;
    case '\e':
        state = STATE_ESC;
        break;
    case '\t':
        console_write(NULL, "    ", 4);
        break;

    default:
        // 检测是否需要滚屏
        if (pos >= 4000)
        {
            scroll_up();
        }
        char *ptr = (char *) screen + pos;
        *ptr = c;
        pos += 2;
        break;
    }
}

// 未完善
void state_esc(char c)
{
    if (c == '\e')
    {
        state = STATE_NORMAL;
        return;
    }
    char *ptr = (char *) screen + pos;
    // 检测是否需要滚屏
    if (pos >= 4000)
    {
        scroll_up();
    }
    *ptr++ = c;
    *ptr = 0b10;
    pos += 2;
}

// 写控制台
int console_write(void *dev, char *buf, int count)
{
    int nr = 0;
    while (nr < count)
    {
        char c = buf[nr];
        switch (state)
        {
        case STATE_NORMAL:
            state_normal(c);
            break;
        case STATE_ESC:
            state_esc(c);
            break;
        }
        nr++;
    }
    state = STATE_NORMAL;
    set_cursor();
    return nr;
}

// 将当前控制台信息保存到 con
static void console_save(console_t *con)
{
    con->pos = pos;
    con->state = state;
    con->screen = screen;
}

static void console_sync()
{
    set_screen();
    set_cursor();
}

static void set_console(console_t *con)
{
    pos = con->pos;
    screen = con->screen;
    state = con->state;
    console_sync();
}

void console_switch(int dev)
{
    console_t *next = &console_table[dev];
    // DEBUGK("next screen %p pos:%d", next->screen, next->pos);

    console_save(current_console);
    set_console(next);
    current_console = next;
}

void console_clear()
{
    u16 *video = (u16 *) screen;
    for (size_t i = 0; i < 2000; i++)
    {
        video[i] = 0x720; // 空格
    }
    pos = 0;
    set_cursor();
}

int get_current_console()
{
    return current_console == console_table ? 0 : 1;
}

void console_init()
{
    for (size_t i = 0; i < 2; i++)
    {
        console_t *con = console_table + i;
        con->screen = MEM_BASE + i * 0x4000;
        con->pos = 0;
        con->state = STATE_NORMAL;
    }

    current_console = console_table;
    set_console(current_console);

    console_clear();

    device_install("console", NULL, NULL, console_write, NULL, DEV_CHAR, DF_WRITE);
}